﻿/// ===========================================================================
/// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
/// KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
/// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
/// PURPOSE.
/// ===========================================================================
/// 
/// Project:        Connected Lookup
/// Author:         Leonid Lyublinski 
/// Date:           07/01/2007  Version:        1.0
///
/// ===========================================================================


using System;
using System.Text;
using System.Data;
using System.Runtime.InteropServices;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;


namespace MayaIntl.SharePoint.ApplicationPages
{
    [System.Web.AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [System.Web.AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [Microsoft.SharePoint.Security.SharePointPermission(System.Security.Permissions.SecurityAction.LinkDemand, ObjectModel = true)]
    [Microsoft.SharePoint.Security.SharePointPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, ObjectModel = true)]
    public class ConnectedLookupFieldControl : LookupField
    {
        #region Constants
        private const string PREFIX_REGISTR = "ConnectedLookupRegistr";
        private const string PREFIX_SELECT = "ConnectedLookupSelect";
        private const string INFINITE_LOOP = "Suspicion about the infinite loop in BuildHierarchy.";
        #endregion

        #region Fields
        private DropDownList _ddlist;
        private ConnectedLookupField _connectedLookupField;
        private string _lookupList;
        private string _lookupField;
        private string _parentField;
        private Guid _lookupWebId;
        private string _siteUrl;
        private ArrayList _parentFields;
        private ArrayList _childFields;
        private byte _counter;
        private DataView _dataSource;
        #endregion

        #region Constructors
        public ConnectedLookupFieldControl()
            : base()
        {
        }
        #endregion

        #region Properties
        /// <summary>
        /// Get the name of the Page storage for control registration
        /// </summary>
        internal string StorageRegistrId
        {
            get
            {
                return string.Format("{0}_{1}", PREFIX_REGISTR, _lookupList);
            }
        }
        /// <summary>
        /// Get the name of the Page storage for control selection
        /// </summary>
        internal string StorageSelectId
        {
            get
            {
                return string.Format("{0}_{1}", PREFIX_SELECT, _lookupList);
            }
        }
        /// <summary>
        /// Makes sure to allow multithreading in case there are many fields on the page. 
        /// Perhaphs,it's overkill.
        /// </summary>
        internal Hashtable StorageRegistr
        {
            get
            {
                if (Page.Items[StorageRegistrId] == null)
                {
                    Page.Items.Add(StorageRegistrId, new Hashtable());
                }
                Hashtable ht = (Hashtable)Page.Items[StorageRegistrId];
                return Hashtable.Synchronized(ht);
            }
        }
        /// <summary>
        /// Makes sure to allow multithreading in case there are many fields on the page. 
        /// Perhaphs,it's overkill.
        /// </summary>
        internal Hashtable StorageSelect
        {
            get
            {
                if (Page.Items[StorageSelectId] == null)
                {
                    Page.Items.Add(StorageSelectId, new Hashtable());
                }
                Hashtable ht = (Hashtable)Page.Items[StorageSelectId];
                return Hashtable.Synchronized(ht);
            }
        }

        /// <summary>
        /// Returns Dataview for dropdown binding, after checking the cached version
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// Dataview is returned by SPQuery running CAML clause. Added (none):0 pair. 
        /// Filtering out only unique Text items. Value is disregraded for uniqueness.
        /// Depends on _lookupField and _lookupList
        /// </remarks>
        internal DataView DataSource
        {
            get
            {
                Guid lookupListId = Common.Utility.ParseGuid(_lookupList);
                SPWeb web = Common.Utility.GetWeb(Context, _siteUrl);
                SPList lookupList = web.Lists[lookupListId];

                {
                    // construct view for the selected columns with order
                    SPQuery query = new SPQuery();
                    query.ViewXml = GetView();

                    // get the referenced data.
                    // IMPORTANT: list is sorted by text!
                    SPListItemCollection listItems = lookupList.GetItems(query);
                    SPField lookupField = lookupList.Fields.GetField(_lookupField);
                    // Create a table to store data for the DropDownList control.
                    DataTable dt = new DataTable();
                    dt.Locale = CultureInfo.InvariantCulture;
                    dt.Columns.Add(new DataColumn("ValueField", typeof(int)));
                    dt.Columns.Add(new DataColumn("TextField", typeof(string)));

                    if (!_connectedLookupField.Required && !_connectedLookupField.AllowMultipleValues)
                    { // Add a (None) option to represent empty
                        DataRow dr = dt.NewRow();
                        dr[0] = SPItem.InvalidItemId;
                        dr[1] = SPResource.GetString(Strings.LookupFieldNoneOption);
                        dt.Rows.Add(dr);
                    }
                    // Populate the data table from the lookup list, making list unique
                    string lookupFieldDataPrevious = string.Empty;
                    if (listItems.Count > 0)
                    {
                        lookupFieldDataPrevious = lookupField.GetFieldValueAsText(listItems[0][_lookupField]);
                        DataRow dr = dt.NewRow();
                        dr[0] = listItems[0].ID;
                        dr[1] = lookupFieldDataPrevious;
                        dt.Rows.Add(dr);
                    }
                    for (Int32 i = 0; i < listItems.Count; i++)
                    {
                        string lookupFieldData = lookupField.GetFieldValueAsText(listItems[i][_lookupField]);
                        if (string.IsNullOrEmpty(lookupFieldData))
                            continue;
                        if (lookupFieldData == lookupFieldDataPrevious)
                            continue;
                        else
                            lookupFieldDataPrevious = lookupFieldData;

                        DataRow dr = dt.NewRow();
                        dr[0] = listItems[i].ID;
                        dr[1] = lookupFieldData;
                        dt.Rows.Add(dr);
                    }

                    // Create a DataView from the DataTable to act as the data source
                    // for the DropDownList control.
                    _dataSource = new DataView(dt);
                    return _dataSource;
                }
            }
        }
        #endregion

        #region Overrides
        /// <summary>
        /// Fires in all events #1
        /// </summary>
        /// <param name="e"></param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            InitConnectedLookupField();
            if (ControlMode == SPControlMode.Edit ||
                ControlMode == SPControlMode.New)
            {
                RegisterConnections();
                ActivatePostBack();
            }
        }

        /// <summary>
        /// Fires on change selection and on save #2
        /// </summary>
        /// <remarks>This doesn't fire during New item
        /// </remarks>
        protected override void CreateChildControls()
        {
            //base.CreateChildControls();
            Controls.Clear();

            try
            {
                _ddlist = new DropDownList();
                _ddlist.TabIndex = TabIndex;
                _ddlist.DataValueField = "ValueField";
                _ddlist.DataTextField = "TextField";
                _ddlist.ToolTip = Field.Title;
                _ddlist.DataSource = DataSource;
                _ddlist.DataBind();
                if (_connectedLookupField.CanBeParent)
                {
                    _ddlist.AutoPostBack = true;
                    _ddlist.SelectedIndexChanged += new EventHandler(ConnectedLookupField_SelectedIndexChanged);
                }
                // Add the DropDownList control to the Controls collection
                Controls.Add(_ddlist);
                SetSelectedItem();
            }
            catch (Exception ex)
            {
                Label error = new Label();
                error.EnableViewState = false;
                error.Text = ex.Message;
                error.CssClass = "ms-error";
                Controls.Add(error);
            }
        }

        /// <summary>
        /// Fires on all events #3
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            if (ControlMode == SPControlMode.Edit ||
                ControlMode == SPControlMode.New)
            {
                SyncStorageSelect();
            }
        }

        /// <summary>
        /// Fires on all events except save #last
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            // connected lookup hierarchy is stored by now for all fields
            if (ControlMode == SPControlMode.Edit ||
                ControlMode == SPControlMode.New)
            {
                BuildHierarchyUp(_lookupField); //ShowParents();
                UpdateBinding();
            }
        }

        /// <summary>
        /// Fires on selection change and save #3
        /// </summary>
        public override void UpdateFieldValueInItem()
        {
            this.EnsureChildControls();

            this.Value = _ddlist.SelectedValue;
            this.ItemFieldValue = this.Value;

        }

        /// <summary>
        /// 
        /// </summary>
        public override void Validate()
        {
            if (ControlMode == SPControlMode.Display || !IsValid )
                return;

            base.Validate();

            if (_connectedLookupField.Required)
            {
                if (string.IsNullOrEmpty(_ddlist.SelectedValue))
                {
                    IsValid = false;
                    this.ErrorMessage =
                        SPResource.GetString(Strings.MissingRequiredField);
                    return;
                }
            }
        }
        #endregion

        #region Dropdown event handlers
        /// <summary>
        /// Stores selection on the Page
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void ConnectedLookupField_SelectedIndexChanged(object sender, EventArgs e)
        {
            // update all children to force null in dependent dropdowns
            // StorageSelect is known by this point,let's find children
            BuildHierarchyDown(_lookupField);
            foreach (string childField in _childFields)
            {
                SyncStorageSelect(childField, null);
            }
            //this.Value = _ddlist.SelectedItem.Text;
        }

        #endregion

        #region Initialization
        /// <summary>
        /// Initializes a ConnectedLookupField object and populates fields
        /// </summary>
        private void InitConnectedLookupField()
        {
            _connectedLookupField = (ConnectedLookupField)Field;
            _lookupList = _connectedLookupField.LookupList;
            _lookupField = _connectedLookupField.LookupField;
            _parentField = _connectedLookupField.ParentField;
            _lookupWebId = _connectedLookupField.LookupWebId;
            _siteUrl = _connectedLookupField.SiteUrl;
        }        
        #endregion

        #region Page Communication
        /// <summary>
        /// Notifies Page that there is a Connected Lookup Field by updating Page.Items collection.
        /// </summary>
        private void RegisterConnections()
        {
            lock (StorageRegistr.SyncRoot)
            {
                StorageRegistr.Add(_lookupField, _parentField);
            }
        }
        /// <summary>
        /// For parent fields will broadcast value
        /// </summary>
        private void SyncStorageSelect()
        {
            string key = _lookupField;
            string val = null;
            if (_ddlist != null)
            {
                val = (_ddlist.SelectedIndex > 0) ? _ddlist.SelectedItem.Text : null; //latest addition - to test
            }
            if (_connectedLookupField.CanBeParent)
            {
                SyncStorageSelect(key, val);
            }
        }
        private void SyncStorageSelect(string key, string val)
        {
            lock (StorageSelect.SyncRoot)
            {
                if (_ddlist != null)
                {
                    if (_ddlist.SelectedIndex > 0)
                        if (StorageSelect.ContainsKey(key))
                            StorageSelect[key] = val;
                        else
                            StorageSelect.Add(key, val);
                    else
                        if (StorageSelect.ContainsKey(key))
                            StorageSelect[key] = null;
                        else
                            StorageSelect.Add(key, null);
                    return;
                }

            }
        }

        /// <summary>
        /// Builds a hierarchy of the dependencies by populating list of parents.
        /// </summary>
        private void BuildHierarchyUp(string childField)
        {
            _counter++; // safety measure against infinite loop. Should limit the
            if (_counter > 200) throw new SPException(INFINITE_LOOP);
            Hashtable nvc = (Hashtable)StorageRegistr.Clone();

            if (string.IsNullOrEmpty(childField)) return;

            if (!nvc.Contains(childField)) return;

            if (_parentFields == null) _parentFields = new ArrayList();

            //let's walk up the tree. will add only not null parents
            string parentField = nvc[childField] as string;
            if (string.IsNullOrEmpty(parentField)) return; // assuming empty string, not "0"

            _parentFields.Add(parentField);
            BuildHierarchyUp(parentField);
        }

        /// <summary>
        /// Builds a hierarchy of the dependencies by populating list of children.
        /// </summary>
        private void BuildHierarchyDown(string parentField)
        {
            _counter++; // safety measure against infinite loop. Should limit the
            if (_counter > 200) throw new SPException(INFINITE_LOOP);
            Hashtable nvc = (Hashtable)StorageRegistr.Clone();

            if (string.IsNullOrEmpty(parentField)) return;
            // find immediate children
            if (_childFields == null) _childFields = new ArrayList();
            foreach (object key in nvc.Keys)
            {
                string childField = key.ToString();
                if (nvc[key].ToString() == parentField)
                    if (!_childFields.Contains(childField))
                    {
                        _childFields.Add(childField);
                        BuildHierarchyDown(childField);
                    }

            }
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Adds SelectedIndexChanged for the dropdown, which broadcats the selected value
        /// </summary>
        private void ActivatePostBack()
        {
            if (_ddlist != null)
            {
                if (_connectedLookupField.CanBeParent)
                {
                    _ddlist.AutoPostBack = true;
                    _ddlist.SelectedIndexChanged += new EventHandler(ConnectedLookupField_SelectedIndexChanged);
                }
            }
        }


        /// <summary>
        /// Binds dropdown to the view of distinct values
        /// </summary>
        private void UpdateBinding()
        {
            if (_ddlist != null)
            {
                string selectedValue = _ddlist.SelectedValue;
                // next line was removed to fight against Selected error
                //if (_ddlist.Items.Count > 0) _ddlist.SelectedValue = _ddlist.Items[0].Value; // otherwise there will be exception during databinding.
                //DataView view = (DataView)_ddlist.DataSource;
                //DataView distinctView = view.ToTable(true, "TextField").DefaultView;
                //distinctView.Sort = "TextField";
                _ddlist.DataSource = DataSource;
                _ddlist.DataTextField = "TextField";
                //_ddlist.DataValueField = "TextField";
                _ddlist.DataBind();
                _ddlist.Enabled = (_ddlist.Items.Count > 0); // to settle for empty list
                // add handling of the selected item
                if (_ddlist.Items.FindByValue(selectedValue) == null)
                {
                    //_ddlist.SelectedIndex = 0;
                    SyncStorageSelect();
                }
                else
                    _ddlist.SelectedValue = selectedValue;
            }
        }

        /// <summary>
        /// Checks the ItemFieldValue for pre-set value and tries to set selected item in the dropdown
        /// </summary>
        private void SetSelectedItem()
        {
            if (ItemFieldValue != null)
            {
                int value = ((SPFieldLookupValue)ItemFieldValue).LookupId;
                string text = ((SPFieldLookupValue)ItemFieldValue).LookupValue;
                if (_ddlist.Items.FindByValue(value.ToString()) != null)
                    _ddlist.SelectedValue = value.ToString();

            }
        }

        private string GetUnifiedName(string name)
        {
            return string.Format("{0}_{1}", _lookupList, name);
        }
        private void ShowParents()
        {
            Controls.Add(new LiteralControl("<XMP>" + GetView() + "</XMP>"));
        }
        #endregion

        #region CAML
        //***************REVISE USING STRINGBUILDER******************//
        private string GetViewFields()
        {
            string result = string.Format(@"<ViewFields><FieldRef Name=""{0}""/></ViewFields>", _lookupField);
            return result;
        }

        /// <summary>
        /// The method shouldparse odd and even numbers differently
        /// </summary>
        /// <returns></returns>
        private string GetWhere()
        {
            ArrayList alWhere = new ArrayList();
            string where = string.Empty;

            if (_parentFields == null) return where;

            for (int i = 0; i < _parentFields.Count; i++)
            {
                string criteria;
                string key = _parentFields[i].ToString();
                string val = StorageSelect[key] as string;
                string fieldRef = string.Format(@"<FieldRef Name=""{0}""/>", key);
                if (!string.IsNullOrEmpty(val))
                {
                    string value = string.Format(@"<Value Type=""Text"">{0}</Value>", val);
                    criteria = "<Eq>" + fieldRef + value + "</Eq>";
                }
                else
                {
                    criteria = "<IsNull>" + fieldRef + "</IsNull>";
                }
                alWhere.Add(criteria);
            }

            // add additional <and> for even elements within
            string compound = string.Empty;
            for (int i = 0; i < alWhere.Count; i++)
            {
                // do for all previous even unless it's a last element
                if ((i > 0) && (i % 2 == 0))
                {
                    compound = string.Concat("<And>", compound, "</And>");
                }
                compound += alWhere[i];
            }

            if (alWhere.Count > 1)
            {
                //where = "<Where><And>" + Common.Utility.GetDelimitedString("", alWhere) + "</And></Where>";
                where = string.Concat("<Where><And>", compound, "</And></Where>");
                return where;
            }
            if (alWhere.Count > 0)
                where = string.Concat("<Where>", compound, "</Where>");
            //where = "<Where>" + Common.Utility.GetDelimitedString("", alWhere) + "</Where>";

            return where;
        }

        private string GetOrderBy()
        {
            string result = string.Format(@"<OrderBy><FieldRef Ascending = ""TRUE"" Name = ""{0}"" /></OrderBy>", _lookupField);
            return result;
        }

        private string GetQuery()
        {
            string result = "<Query>";
            result += GetOrderBy();
            result += GetWhere();
            result += "</Query>";
            return result;
        }

        private string GetView()
        {
            string result = @"<View Scope=""RecursiveAll"">";
            result += GetQuery();
            result += GetViewFields();
            result += "</View>";
            return result;
        }
        #endregion

    }
}
